using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using SimpleServiceBus.Bus.MessageManagement.Serialization;
using SimpleServiceBus.Endpoints.ActiveMQ.Transport;
using System.Globalization;
using SimpleServiceBus.Bus.MessageManagement.MessageHandling;
using System.Reflection;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Bus.Transport;
using Apache.NMS;
using SimpleServiceBus.Bus.MessageManagement;

namespace SimpleServiceBus.Endpoints.ActiveMQ.Configuration {

    public class ActiveMQConfigurator {

        //-----< Fields >-----
        //----------------------------------------------------------------------------------        
        private String fEndpointId;
        private String fBrokerUrl;
        private String fUserName;
        private String fPassword;
        private IMessageSerializer fMessageSerializer;
        private MessageFormat fMessageFormat;
        private String fDefaultQueueName;
        private ActiveMQConsumerPoolConfigurator fDefaultConsumerPool;
        private Dictionary<String, ActiveMQConsumerPoolConfigurator> fConsumerPools = new Dictionary<String, ActiveMQConsumerPoolConfigurator>();
        private ActiveMQMessageHandlerConfigurator fMessageHandlers;
        private ActiveMQMessageDestinationConfigurator fMessageDestinations = new ActiveMQMessageDestinationConfigurator();
        private IDestinationResolver fDestinationResolver;
        private ActiveMQMessageTypeConfigurator fMessageTypes = new ActiveMQMessageTypeConfigurator();
        private ActiveMQLocalServiceConfigurator fLocalServices = new ActiveMQLocalServiceConfigurator();


        //-----< Constructors >-----
        //----------------------------------------------------------------------------------        
        public ActiveMQConfigurator(String endpointId) {
            Initialize(endpointId);
        }

        public ActiveMQConfigurator(String endpointId, String defaultQueueName) {
            if(String.IsNullOrEmpty(defaultQueueName))
                throw new ArgumentNullException("defaultQueueName");
            fDefaultQueueName = defaultQueueName;
            Initialize(endpointId);
        }

        //-----< Public and protected properties >-----
        //----------------------------------------------------------------------------------        

        public IDictionary<String, ActiveMQConsumerPoolConfigurator> ConsumerPools {
            get { return fConsumerPools; }
        }
        public ActiveMQConsumerPoolConfigurator DefaultConsumerPool {
            get { return fDefaultConsumerPool; }
        }

        public String DefaultQueueName {
            get { return fDefaultQueueName; }
        }

        public IDestinationResolver DestinationResolver {
            get { return fDestinationResolver; }
            set { fDestinationResolver = value; }
        }

        public String EndpointId {
            get { return fEndpointId; }
        }

        public ActiveMQMessageDestinationConfigurator MessageDestinations {
            get { return fMessageDestinations; }
        }

        public ActiveMQMessageHandlerConfigurator MessageHandlers {
            get {
                if(fMessageHandlers == null) 
                    fMessageHandlers = CreateMessageHandlerConfigurator();
                return fMessageHandlers; 
            }
        }

        public ActiveMQMessageTypeConfigurator MessageTypes {
            get { return fMessageTypes; }
        }

        public ActiveMQLocalServiceConfigurator LocalServices {
            get { return fLocalServices; }
        }

        //-----< Public and protected methods >-----
        //----------------------------------------------------------------------------------        

        public virtual IMessagingEndpoint CreateEndpoint() {
            if(String.IsNullOrEmpty(fBrokerUrl))
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "You must specify the broker URL to connect to"));
            if(String.IsNullOrEmpty(fDefaultQueueName)) {
                foreach(var _pool in fConsumerPools.Values)
                    if(_pool.Queues.FirstOrDefault() != null || _pool.Topics.FirstOrDefault() != null)
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "You cannot consume queues in SendOnly mode (you didn't provide a default queue name)"));
            }
            var _builder = ActiveMQEndpointConfiguration.ConfigureEndpoint();
            if(fMessageSerializer != null)
                _builder.RegisterService<IMessageSerializer>(fMessageSerializer);
            var _endpoint = _builder.CreateEndpoint();
            _builder.Metadata.EndpointID = fEndpointId;
            _builder.Metadata.ReturnAddress = fDefaultQueueName;
            _builder.Metadata.SendOnly = fDefaultQueueName == null;
            var _messageTypes = _builder.MessageTypes;
            foreach(var _type in this.MessageTypes.KnownTypes)
                _messageTypes.RegisterMessageType(_type);
            foreach(var _msgDestination in fMessageDestinations)
                _messageTypes.RegisterMessageType(_msgDestination.Key, _msgDestination.Value);
            var _messageHandlers = _builder.MessageHandlers;
            foreach(var _handler in MessageHandlers) {
                _messageHandlers.RegisterMessageHandler(_handler);
                RegisterMessageTypes(_handler, _messageTypes);
            }
            LocalServices.ExportTo(_endpoint.LocalServices);
            var _transport = (ActiveMQTransport)_endpoint.LocalServices.GetService<ITransport>();
            if(_transport == null)
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot find transport service"));
            _transport.ConnectionFactory = CreateConnectionFactory();
            _transport.MessageFormat = fMessageFormat;
            if(DestinationResolver != null)
                _transport.DestinationResolver = DestinationResolver;
            if(fDefaultQueueName != null)
                _transport.ConsumerPools.Default.AddQueue(new ActiveMQConsumedQueue(fDefaultQueueName));
            foreach(var _srcPool in fConsumerPools) {
                var _destPool = _transport.ConsumerPools[_srcPool.Key];
                if(_destPool == null) {
                    _destPool = new ActiveMQConsumerPool(_srcPool.Key);
                    _transport.ConsumerPools.Add(_destPool);
                }
                _destPool.AcknowledgementMode = _srcPool.Value.AcknowledgementMode;
                _destPool.WorkerCount = _srcPool.Value.WorkerCount;
                foreach(var _srcQueue in _srcPool.Value.Queues) {
                    var _destQueue = new ActiveMQConsumedQueue(_srcQueue.Name);
                    _destQueue.Selector = _srcQueue.Selector;
                    _destQueue.IsExclusive = _srcQueue.IsExclusive;
                    _destQueue.Handler = _srcQueue.Handler;
                    _destPool.AddQueue(_destQueue);
                }
                foreach(var _srcTopic in _srcPool.Value.Topics) {
                    var _destTopic = new ActiveMQConsumedTopic(_srcTopic.Name);
                    _destTopic.Selector = _srcTopic.Selector;
                    _destTopic.IsExclusive = _srcTopic.IsExclusive;
                    _destTopic.NoLocal = _srcTopic.NoLocal;
                    _destTopic.DurableSubscriptionName = _srcTopic.DurableSubscriptionName;
                    _destTopic.Handler = _srcTopic.Handler;
                    _destPool.AddTopic(_destTopic);
                }
                if(_destPool.HasTopics)
                    _destPool.WorkerCount = 1;
            }
            return _endpoint;
        }

        protected virtual void RegisterMessageTypes(Type handlerType, IMessageTypeContainer messageTypeContainer) {
            foreach(var _messageType in handlerType.GetAllMessageTypesHandled())
                if(!_messageType.IsAbstract) {
                    messageTypeContainer.RegisterMessageType(_messageType);
                }
        }

        protected virtual ActiveMQMessageHandlerConfigurator CreateMessageHandlerConfigurator() {
            return new ActiveMQMessageHandlerConfigurator();
        }

        protected virtual IConnectionFactory CreateConnectionFactory() {
            var _factory = new Apache.NMS.ActiveMQ.ConnectionFactory(fBrokerUrl, fEndpointId);
            _factory.UserName = fUserName;
            _factory.Password = fPassword;
            return _factory;
        }

        public virtual ActiveMQConfigurator ConnectTo(String brokerUrl) {
            if(brokerUrl == null)
                throw new ArgumentNullException("brokerUrl");
            fBrokerUrl = brokerUrl;
            return this;
        }
        
        public virtual ActiveMQConfigurator UseCrendentials(String userName, String password) {
            if(userName == null)
                throw new ArgumentNullException("userName");
            fUserName = userName;
            fPassword = password;
            return this;
        }

        public ActiveMQConfigurator ResolveDestinationsWith(IDestinationResolver resolver) {
            DestinationResolver = resolver;
            return this;
        }

        public ActiveMQConfigurator SerializeMessagesWith(IMessageSerializer serializer) {
            if(serializer == null)
                throw new ArgumentNullException("serializer");
            fMessageSerializer = serializer;
            return this;
        }

        public ActiveMQConfigurator FormatMessagesUsing(MessageFormat messageFormat) {
            fMessageFormat = messageFormat;
            return this;
        }

        public ActiveMQConfigurator SetupConsumerPool(String poolId, Action<ActiveMQConsumerPoolConfigurator> action) {
            if(String.IsNullOrEmpty(poolId))
                throw new ArgumentNullException("poolId");
            if(action == null)
                throw new ArgumentNullException("action");
            ActiveMQConsumerPoolConfigurator _pool;
            if(!fConsumerPools.TryGetValue(poolId, out _pool)) {
                _pool = new ActiveMQConsumerPoolConfigurator();
                fConsumerPools[poolId] = _pool;
            }
            action(_pool);
            return this;
        }
        
        public ActiveMQConfigurator SetupDefaultConsumerPool(Action<ActiveMQConsumerPoolConfigurator> action) {
            return SetupConsumerPool(ActiveMQConsumerPoolCollection.DefaultConsumerPoolId, action);
        }

        public ActiveMQConfigurator SetupMessageDestinations(Action<ActiveMQMessageDestinationConfigurator> action) {
            if(action == null)
                throw new ArgumentNullException("action");
            action(fMessageDestinations);
            return this;
        }

        public ActiveMQConfigurator SetupMessageHandlers(Action<ActiveMQMessageHandlerConfigurator> action) {
            if(action == null)
                throw new ArgumentNullException("action");
            action(MessageHandlers);
            return this;
        }
        
        public ActiveMQConfigurator SetupMessageTypes(Action<ActiveMQMessageTypeConfigurator> action) {
            if(action == null)
                throw new ArgumentNullException("action");
            action(MessageTypes);
            return this;
        }

        public ActiveMQConfigurator SetupLocalServices(Action<ActiveMQLocalServiceConfigurator> action) {
            if(action == null)
                throw new ArgumentNullException("action");
            action(LocalServices);
            return this;
        }

        //-----< Events >-----
        //----------------------------------------------------------------------------------        


        //-----< Internal members >-----
        //----------------------------------------------------------------------------------        


        //-----< Private members >-----
        //----------------------------------------------------------------------------------        

        private void Initialize(String endpointId) {
            if(endpointId == null)
                throw new ArgumentNullException("endpointId");
            fEndpointId = endpointId;
            fDefaultConsumerPool = new ActiveMQConsumerPoolConfigurator();
            fDefaultConsumerPool.WorkerCount = 5;
            fConsumerPools[ActiveMQConsumerPoolCollection.DefaultConsumerPoolId] = fDefaultConsumerPool;
        }

        //-----< All nested types >-----
        //----------------------------------------------------------------------------------        


        //-----< All explicit interface implementations >-----
        //----------------------------------------------------------------------------------        


    }
}

